package com.enlf.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.enlf.dto.LoginFormDTO;
import com.enlf.dto.R;
import com.enlf.dto.Result;
import com.enlf.dto.UserDTO;
import com.enlf.entity.MenuPermission;
import com.enlf.entity.User;
import com.enlf.entity.UserInfo;
import com.enlf.mapper.MenuPermissionMapper;
import com.enlf.mapper.UserInfoMapper;
import com.enlf.mapper.UserMapper;
import com.enlf.service.IUserInfoService;
import com.enlf.service.IUserService;
import com.enlf.utils.CodeEnum;
import com.enlf.utils.EnlfConstants;
import com.enlf.utils.RegexUtils;
import com.enlf.utils.UserHolder;
import com.enlf.vo.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.enlf.utils.RedisConstants.*;
import static com.enlf.utils.SystemConstants.USER_NICK_NAME_PREFIX;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author renge
 * @since 2023-9-11
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private MenuPermissionMapper menuPermissionMapper;

    @Resource
    private IUserInfoService userInfoService;

    @Resource
    private UserInfoMapper userInfoMapper;

    @Resource
    private UserMapper userMapper;
    @Override
    public Result sentCode(String phone, HttpSession session) {
        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            //如果失败就返回error
            return Result.fail("手机号输入有误");
        }
        //2.生成6位验证码
        String code = RandomUtil.randomNumbers(6);

        //3.保存验证码到redis
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

        //4.发送验证码
        //todo 验证码需要接入阿里云
        if (log.isDebugEnabled()) {
            log.debug("发送验证码成功，验证码为{}",code);
        }
        return Result.ok();
    }

    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1.再次校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号输入错误");
        }
        //2.校验验证码或密码
        if(null != loginForm.getCode()){
            String code = loginForm.getCode();
            String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
            if (!code.equals(cacheCode)){
                //失败返回错误
                return Result.fail("验证码错误");
            }
        }
        if (null != loginForm.getPassword()){
            String password = loginForm.getPassword();
            User user = query().eq("phone", phone).one();
            if (user == null || !password.equals(user.getPassword())){
                return Result.fail("密码错误");
            }
        }

        //3.通过mybatis plus查询手机号是否存在
        User user = query().eq("phone", phone).one();
        //4.不存在创建新用户
        if (user == null){
            //不存在就创建新用户 ： 昵称，默认是随机字符  和   电话
            user = createUserWithPhone(phone);
        }

        //5.保存用户到redis中
        //5.1 使用uuid生成随机token
        String token = UUID.randomUUID().toString(true);
        //5.2 将user对象转化为hash存储
        UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);
        //这里是因为StringRedisTemplate要求map的所有值为string
        //而我们的userDTO的id为long类型,我们需要做进一步处理
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                //数据copy的选项
                CopyOptions.create()
                        //是否忽略空值
                        .setIgnoreNullValue(true)
                        //设置修改器 将所有的值都toString
                        .setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
        //5.3 存储
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
        //5.4 设置有效期
        stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
        //返回token
        return Result.ok(token);
    }

    /**
     * 创建新用户
     * @param phone
     * @return
     */
    private User createUserWithPhone(String phone) {

        //1.创建user对象
        User user = new User();
        user.setPhone(phone);
        //2.生成随机昵称
        String nickName = "user_" + RandomUtil.randomString(10);
        user.setNickName(nickName);

        save(user);
        return user;
    }

    @Override
    public Map<String,String> loginAdmin(LoginFormDTO loginForm, HttpSession session) {
        Map<String,String> result = new HashMap<>();
        //1.再次校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            result.put("code",CodeEnum.FAIL.getCode()+"");
            result.put("msg","手机号输入错误");
            return result;
        }
        //2.校验验证码或密码
        if(null != loginForm.getCode()){
            String code = loginForm.getCode();
            String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
            if (!code.equals(cacheCode)){
                //失败返回错误
                result.put("code",CodeEnum.PHONE_FAIL.getCode()+"");
                result.put("msg","验证码错误");
                return result;
            }
        }
        if (null != loginForm.getPassword()){
            String password = loginForm.getPassword();
            User user = query().eq("phone", phone).one();
            if (user == null || !password.equals(user.getPassword())){
                result.put("code",CodeEnum.PHONE_FAIL.getCode()+"");
                result.put("msg","密码错误");
                return result;
            }
        }

        //3.通过mybatis plus查询手机号是否存在
        User user = query().eq("phone", phone).one();
        //4.不存在创建新用户
        if (user == null){
            //不存在就创建新用户 ： 昵称，默认是随机字符  和   电话
            user = createUserWithPhone(phone);
        }

        //5.保存用户到redis中
        //5.1 使用uuid生成随机token
        String token = UUID.randomUUID().toString(true);
        //5.2 将user对象转化为hash存储
        UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);
        //这里是因为StringRedisTemplate要求map的所有值为string
        //而我们的userDTO的id为long类型,我们需要做进一步处理
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                //数据copy的选项
                CopyOptions.create()
                        //是否忽略空值
                        .setIgnoreNullValue(true)
                        //设置修改器 将所有的值都toString
                        .setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
        //5.3 存储
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
        //5.4 设置有效期
        stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
        //返回token
        result.put("code",CodeEnum.OK.getCode()+"");
        result.put("msg","登录成功");
        result.put("token",token);
        return result;
    }

    @Override
    public List<MenuPermission> queryAdminPermission(Long userId) {
        List<MenuPermission> menuPermissions = menuPermissionMapper.queryMenuByUserId(userId);
        return menuPermissions;
    }

    @Override
    public List<User> queryAll() {
        List<User> userList = query().list();
        return userList;
    }

    /**
     * 创建新用户
     * @param phone
     * @return
     */
    private User createAdminUser(String phone) {

        //1.创建user对象
        User user = new User();
        user.setPhone(phone);
        //2.生成随机昵称
        String nickName = "user_" + RandomUtil.randomString(10);
        user.setNickName(nickName);

        save(user);
        return user;
    }
    @Override
    public Result sign() {
        // 1.获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        // 2.获取日期
        LocalDateTime now = LocalDateTime.now();
        // 3.拼接key
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = USER_SIGN_KEY + userId + keySuffix;
        // 4.获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        // 5.写入Redis SETBIT key offset 1
        stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
        return Result.ok();
    }

    @Override
    public Result signCount() {
        // 1.获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        // 2.获取日期
        LocalDateTime now = LocalDateTime.now();
        // 3.拼接key
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = USER_SIGN_KEY + userId + keySuffix;
        // 4.获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        // 5.获取本月截止今天为止的所有的签到记录，返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0
        List<Long> result = stringRedisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if (result == null || result.isEmpty()) {
            // 没有任何签到结果
            return Result.ok(0);
        }
        Long num = result.get(0);
        if (num == null || num == 0) {
            return Result.ok(0);
        }
        // 6.循环遍历
        int count = 0;
        while (true) {
            // 6.1.让这个数字与1做与运算，得到数字的最后一个bit位  // 判断这个bit位是否为0
            if ((num & 1) == 0) {
                // 如果为0，说明未签到，结束
                break;
            }else {
                // 如果不为0，说明已签到，计数器+1
                count++;
            }
            // 把数字右移一位，抛弃最后一个bit位，继续下一个bit位
            num >>>= 1;
        }
        return Result.ok(count);
    }

    @Override
    @Transactional
    public R updateUser(UserInfoVo userInfoVo) {
        LocalDateTime now = LocalDateTime.now();

        User user = new User();
        user.setId(userInfoVo.getId());
        user.setPhone(userInfoVo.getPhone());
        user.setNickName(userInfoVo.getNickName());
        user.setUpdateTime(now);
        user.setPassword(userInfoVo.getPassword() != null ? userInfoVo.getPassword() : null);
        updateById(user);
        UserInfo userInfo = userInfoService.getById(userInfoVo.getId());

        if (userInfo == null) {
            userInfo = new UserInfo();
            userInfo.setCreateTime(now);
        }
        userInfo.setUserId(userInfoVo.getId());
        userInfo.setGender(userInfoVo.getGender());
        userInfo.setUpdateTime(now);
        userInfo.setCity(userInfoVo.getCity() != null ? userInfoVo.getCity() : null); // 非空判断
        userInfo.setLevel(userInfoVo.getLevel() != null ? userInfoVo.getLevel() : null);
        userInfo.setBirthday(userInfoVo.getBirthday() != null ? userInfoVo.getBirthday() : null);

        userInfoService.saveOrUpdate(userInfo);

        return R.OK();
    }

    @Override
    public R saveUser(UserInfoVo userInfoVo) {
        User user = BeanUtil.copyProperties(userInfoVo,User.class);
        if (null == userInfoVo.getNickName()){
            String nickName = "user_" + RandomUtil.randomString(10);
            user.setNickName(nickName);
        }
        UserInfo userInfo = BeanUtil.copyProperties(userInfoVo, UserInfo.class);
        try{
            userMapper.insertUser(user);
            userInfo.setUserId(user.getId());
            userInfoMapper.insertUserInfo(userInfo);

        }catch (Exception e){
            log.error(e.getMessage());
            return R.FAIL();
        }
        return R.OK();
    }

}
